package me.haeger.assembler;

import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Scanner;
import java.util.regex.Pattern;

/**
 *
 * @author Häger
 */
public class Encoder {
    
    private final String assemblyText;
    private final String WHITESPACECHARS = "[ \t\n\f\r]";
    
    private LinkedList<Instruction> instructionList;
    
    public Encoder(String assemblyText) {
        this.assemblyText = assemblyText.toLowerCase();
        this.instructionList = new LinkedList();
        
    }
    
    public void assemble() {
        System.out.print("Parsing... ");
        int rowCounter = 1;
        Scanner text = new Scanner(assemblyText);
        text.useDelimiter(System.getProperty("line.separator"));
        String line;
        try {
            while(text.hasNextLine()){
                line = removeComment(text.next());
                if(!line.isEmpty())
                    instructionList.add(readLine(line));

                rowCounter++;
            }
        } catch (NoSuchElementException ex) {
            System.out.println("Done.");            
        } catch (IllegalArgumentException ex) {
            System.err.println("\nCOMPILE ERROR\nRow: " + rowCounter + 
                    System.lineSeparator() + ex.getMessage());
            instructionList.clear();
        }        
        System.out.println("Assembly completed.");
    }
    
    public String print() {
        int i = 1;
        StringBuilder s = new StringBuilder();
        for(Instruction instr : instructionList) {
            if (i == 512){
                s.append(instr.easyPrint()).append(System.lineSeparator());
                i = 1;
                System.out.println("");  
            }          
            else {
                s.append(instr.easyPrint()).append(System.lineSeparator());
                i++;
            }  
        }
        return s.toString();
    }
    
    private Instruction readLine(String line) {
        Instruction instr = new Instruction();
        try {            
            Pattern whitespacePattern = Pattern.compile(WHITESPACECHARS);
            Scanner scanner = new Scanner(line);
            scanner.useDelimiter(whitespacePattern);
            String opName = scanner.next();

            scanner.useDelimiter(",");
            switch(opName) {
                case "add" :
                case "sub" :
                case "mul" :
                case "muls" :
                    instr.setrD(getRegister(scanner.next()));
                    instr.setrA(getRegister(scanner.next()));

                    String s = scanner.next();
                    if(isRegister(s)) {
                        instr.setrB(getRegister(s));
                    } else
                        instr.setK(getConstant(s));                    
                    break;
                case "ld" :
                case "st" :
                case "store" :
                case "load" :
                    instr.setrD(getRegister(scanner.next()));
                    instr.setK(getConstant(scanner.next()));
                    break;
                case "move" :
                case "mov" :
                    instr.setrD(getRegister(scanner.next()));
                    instr.setrB(getRegister(scanner.next()));
                    break;
                case "cmp" :
                    instr.setrA(getRegister(scanner.next()));
                    
                    String t = scanner.next();
                    if(isRegister(t)) {
                        instr.setrB(getRegister(t));
                    } else
                        instr.setK(getConstant(t));                    
                    break;
                case "bne" :
                case "beq" :
                case "jump":
                case "jmp" :
                    String bin = instr.makeBinaryString(getConstant(scanner.next()), 25);
                    instr.setrD(Integer.parseInt(bin.substring(0, 5), 2));
                    instr.setrA(Integer.parseInt(bin.substring(5, 10), 2));
                    instr.setrB(Integer.parseInt(bin.substring(10, 15), 2));
                    instr.setK(Integer.parseInt(bin.substring(15, 25), 2));
                    break;
                case "nop" :
                    break;
                default :
                    throw new IllegalArgumentException("Illegal operation.");
            }
            
            instr.setOp(getOpCode(opName, line));
            
        } catch (NoSuchElementException ex) {  // Shouldn't be able to get here
            System.err.print("No matching pattern. ");
            
        } catch (IllegalArgumentException ex) {
            System.err.print(ex.toString());
            throw ex;
        } 
         return instr;
        
    }
    
    private boolean isRegister(String s) {
        return (s.trim().charAt(0) == 'r');        
    }
    
    private int getRegister(String s) {
        if(!isRegister(s))
            throw new IllegalArgumentException("Illegal format while retrieving register.");
        
        return Integer.parseInt(s.trim().substring(1));
    }

    private int getConstant(String s) {
        String constant = s.trim();
                
        if(constant.charAt(0) == '#')
            return Integer.parseInt(constant.substring(1));        
        else if(constant.charAt(0) == 'x')
            return Integer.parseInt(constant.substring(1), 16);
        else if(constant.charAt(0) == 'b')
            return Integer.parseInt(constant.substring(1), 2);
        
        throw new NumberFormatException("Could not convert constant.");
        
    }
    
    private String removeComment(String s) {
        if(s.indexOf(';') != -1) {
            s = s.substring(0, s.indexOf(';'));
        }
        return s.trim();
    }

    private int getOpCode(String opName, String line) {
        
        switch(opName) {
                case "add" :
                    if(hasConstant(line))
                        return 1;
                    else
                        return 9;
                case "sub" :
                    if(hasConstant(line))
                        return 33;
                    else
                        return 41;
                case "mul" :
                case "muls" :
                    if(hasConstant(line))
                        return 65;
                    else
                        return 73;
                case "ld" :
                case "load" :
                    return 101;                    
                case "st" :
                case "store" :
                    return 97;
                case "move" :
                case "mov" :
                    return 105;
                case "cmp" :
                    if(hasConstant(line))
                        return 32;
                    else
                        return 40;
                case "bne" :
                    return 124;
                case "beq" :
                    return 120;
                case "jump":
                case "jmp" :
                    return 112;
                case "nop" :
                    return 96;
                default :
                    throw new IllegalArgumentException("Could not extract OP-code.");
            }     
    }

    private boolean hasConstant(String line) {
        String constant = line.substring(line.lastIndexOf(',')+1).trim();
        try {
            getConstant(constant);            
        } catch (NumberFormatException ex) {
            return false;
        }
        return true;
    }
    
}